use crate::qbus::QBus;
use crate::qconsumer::{QConsumer, QMessageFilter};
use crate::qmessage::{QMessage, INVALID_SEQ};
use core::ffi::{c_char, c_int, c_uint, c_void};
use core::ptr::null_mut;
use embed_std::util::cstr_to_str;
use embed_std::{cstr, errorln};

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct qbus {
    pub priv_: *mut c_void,
    pub free: Option<unsafe extern "C" fn(bus: *mut qbus)>,
    pub publish: Option<
        unsafe extern "C" fn(
            bus: *mut qbus,
            topic: *const c_char,
            payload: *const c_char,
            payload_len: c_uint,
        ) -> c_int,
    >,
    pub publish_ext: ::core::option::Option<
        unsafe extern "C" fn(
            bus: *mut qbus,
            topic: *const ::core::ffi::c_char,
            header: *const ::core::ffi::c_char,
            header_len: ::core::ffi::c_uint,
            payload: *const ::core::ffi::c_char,
            payload_len: ::core::ffi::c_uint,
        ) -> ::core::ffi::c_int,
    >,
    pub call: Option<
        unsafe extern "C" fn(
            bus: *mut qbus,
            topic: *const c_char,
            payload: *const c_char,
            payload_len: c_uint,
            resp: *mut c_char,
            resp_len: c_uint,
            resp_act_len: *mut c_uint,
            timeout: c_uint,
        ) -> c_int,
    >,
}

#[no_mangle]
pub unsafe extern "C" fn create_qbus(limit: c_uint) -> *mut qbus {
    let s = Box::new(qbus {
        priv_: Box::into_raw(Box::new(QBus::new(limit as usize))) as *mut c_void,
        free: Some(free_qbus),
        publish: Some(publish_qmessage),
        publish_ext: Some(publish_ext_qmessage),
        call: Some(call_qmessage),
    });
    Box::into_raw(s)
}

unsafe extern "C" fn free_qbus(bus: *mut qbus) {
    if bus.is_null() {
        return;
    }
    let qb = Box::from_raw(bus as *mut qbus);
    if !qb.priv_.is_null() {
        let bus = Box::from_raw(qb.priv_ as *mut QBus);
        drop(bus);
    }
    drop(qb);
}

macro_rules! QBUS {
    ($bus:expr) => {{
        if $bus.is_null() {
            return -1;
        }
        let qb = Box::leak(Box::from_raw($bus as *mut qbus));
        if qb.priv_.is_null() {
            return -1;
        }
        let qbus = Box::leak(Box::from_raw(qb.priv_ as *mut QBus));
        qbus
    }};
}
unsafe extern "C" fn publish_qmessage(
    bus: *mut qbus,
    topic: *const c_char,
    payload: *const c_char,
    payload_len: c_uint,
) -> c_int {
    if topic.is_null() {
        return -1;
    }
    let qbus = QBUS!(bus);
    let t = cstr_to_str(topic);
    let p = if payload.is_null() || payload_len == 0 {
        &[]
    } else {
        core::slice::from_raw_parts(payload as *const u8, payload_len as usize)
    };
    match qbus.publish(t, p) {
        Ok(()) => 0,
        Err(err) => {
            errorln!("publish {} failed:{}", t, err);
            -2
        }
    }
}
unsafe extern "C" fn publish_ext_qmessage(
    bus: *mut qbus,
    topic: *const c_char,
    header: *const c_char,
    header_len: c_uint,
    payload: *const c_char,
    payload_len: c_uint,
) -> c_int {
    if topic.is_null() {
        return -1;
    }
    let qbus = QBUS!(bus);
    let t = cstr_to_str(topic);
    let h = if header.is_null() || header_len == 0 {
        &[]
    } else {
        core::slice::from_raw_parts(header as *const u8, header_len as usize)
    };
    let p = if payload.is_null() || payload_len == 0 {
        &[]
    } else {
        core::slice::from_raw_parts(payload as *const u8, payload_len as usize)
    };
    match qbus.publish_vec(t, &[h, p]) {
        Ok(()) => 0,
        Err(err) => {
            errorln!("publish_ext {} failed:{}", t, err);
            -2
        }
    }
}
unsafe extern "C" fn call_qmessage(
    bus: *mut qbus,
    topic: *const c_char,
    payload: *const c_char,
    payload_len: c_uint,
    resp: *mut c_char,
    resp_len: c_uint,
    resp_act_len: *mut c_uint,
    timeout: c_uint,
) -> c_int {
    if topic.is_null() {
        return -1;
    }
    let qbus = QBUS!(bus);
    let t = cstr_to_str(topic);
    let p = if payload.is_null() || payload_len == 0 {
        &[]
    } else {
        core::slice::from_raw_parts(payload as *const u8, payload_len as usize)
    };
    match qbus.call(t, p, timeout) {
        Ok(data) => {
            if !resp.is_null() && resp_len > 0 {
                if (resp_len as usize) < data.len() {
                    errorln!("buffer {} vs {} too small", resp_len, data.len());
                    return -1;
                }
                core::ptr::copy(data.as_ptr(), resp as *mut u8, data.len());
                if !resp_act_len.is_null() {
                    *resp_act_len = data.len() as u32;
                }
            }
            0
        }
        Err(err) => {
            errorln!("publish {} failed:{}", t, err);
            -2
        }
    }
}

pub type qreader = Option<
    unsafe extern "C" fn(
        topic: *const c_char,
        topic_len: c_uint,
        payload: *const c_void,
        payload_len: c_uint,
        seq: c_uint,
        args: *mut c_void,
    ),
>;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct qconsumer {
    pub priv_: *mut c_void,
    pub free: Option<unsafe extern "C" fn(consumer: *mut qconsumer)>,
    pub subscribe:
        Option<unsafe extern "C" fn(consumer: *mut qconsumer, topic: *const c_char) -> c_int>,
    pub unsubscribe:
        Option<unsafe extern "C" fn(consumer: *mut qconsumer, topic: *const c_char) -> c_int>,
    pub read: Option<
        unsafe extern "C" fn(
            consumer: *mut qconsumer,
            topic: *mut c_char,
            topic_len: c_uint,
            payload: *mut c_char,
            payload_len: c_uint,
            act_payload_len: *mut c_uint,
            out_seq: *mut c_uint,
        ) -> c_int,
    >,
    pub read_map: Option<
        unsafe extern "C" fn(consumer: *mut qconsumer, reader: qreader, args: *mut c_void) -> c_int,
    >,
    pub send_resp: Option<
        unsafe extern "C" fn(
            bus: *mut qconsumer,
            seq: c_uint,
            payload: *const c_char,
            payload_len: c_uint,
        ) -> c_int,
    >,
}

#[no_mangle]
pub unsafe extern "C" fn create_consumer(bus: *mut qbus) -> *mut qconsumer {
    if bus.is_null() {
        return null_mut();
    }
    let qb = Box::leak(Box::from_raw(bus as *mut qbus));
    if qb.priv_.is_null() {
        return null_mut();
    }
    let qbus = Box::leak(Box::from_raw(qb.priv_ as *mut QBus));
    let p = Box::into_raw(Box::new(qbus.create_consumer()));
    let c = Box::new(qconsumer {
        priv_: p as *mut c_void,
        free: Some(free_qconsumer),
        subscribe: Some(subscribe_consumer),
        unsubscribe: Some(unsubscribe_consumer),
        read: Some(read_qmessage),
        read_map: Some(read_map),
        send_resp: Some(send_qmessage_resp),
    });
    Box::into_raw(c)
}

unsafe extern "C" fn free_qconsumer(consumer: *mut qconsumer) {
    if consumer.is_null() {
        return;
    }
    let c = Box::from_raw(consumer as *mut qconsumer);
    if !c.priv_.is_null() {
        let qc = Box::from_raw(c.priv_ as *mut QConsumer);
        drop(qc);
    }
    drop(c);
}

macro_rules! QC {
    ($consumer:expr) => {{
        if $consumer.is_null() {
            return -1;
        }
        let c = Box::leak(Box::from_raw($consumer as *mut qconsumer));
        if c.priv_.is_null() {
            return -1;
        }
        let c = Box::leak(Box::from_raw(c.priv_ as *mut QConsumer));
        c
    }};
}
unsafe extern "C" fn subscribe_consumer(consumer: *mut qconsumer, topic: *const c_char) -> c_int {
    if topic.is_null() {
        return -1;
    }
    let c = QC!(consumer);
    let t = cstr_to_str(topic);
    c.subscribe(t);
    0
}

unsafe extern "C" fn unsubscribe_consumer(consumer: *mut qconsumer, topic: *const c_char) -> c_int {
    if topic.is_null() {
        return -1;
    }
    let c = QC!(consumer);
    let t = cstr_to_str(topic);
    c.unsubscribe(t);
    0
}

unsafe extern "C" fn read_qmessage(
    consumer: *mut qconsumer,
    topic: *mut c_char,
    topic_len: c_uint,
    payload: *mut c_char,
    payload_len: c_uint,
    act_payload_len: *mut c_uint,
    out_seq: *mut c_uint,
) -> c_int {
    if payload.is_null() || topic.is_null() {
        return -1;
    }
    let c = QC!(consumer);
    let buff = core::slice::from_raw_parts_mut(payload as *mut u8, payload_len as usize);
    if let Some((size, info)) = c.read_to_buff(buff) {
        core::ptr::write_bytes(topic, 0, topic_len as usize);
        let len = if topic_len as usize - 1 < info.topic.len() {
            topic_len as usize - 1
        } else {
            info.topic.len()
        };
        core::ptr::copy(info.topic.as_ptr() as *const c_char, topic, len);
        *topic.offset(len as isize) = 0;

        if !act_payload_len.is_null() {
            *act_payload_len = size as c_uint;
        }
        if info.req_id != INVALID_SEQ && !out_seq.is_null() {
            *out_seq = info.req_id;
        }
        return 0;
    }
    -1
}

unsafe extern "C" fn read_map(
    consumer: *mut qconsumer,
    reader: qreader,
    args: *mut c_void,
) -> c_int {
    let c = QC!(consumer);
    if c.read_map(|msg| {
        if let Some(f) = reader {
            f(
                msg.topic.as_ptr() as *const c_char,
                msg.topic.len() as c_uint,
                msg.payload.as_ptr() as *const c_void,
                msg.payload.len() as c_uint,
                msg.req_id,
                args,
            )
        }
    }) {
        0
    } else {
        -1
    }
}

unsafe extern "C" fn send_qmessage_resp(
    consumer: *mut qconsumer,
    seq: c_uint,
    payload: *const c_char,
    payload_len: c_uint,
) -> c_int {
    let c = QC!(consumer);
    let pl = if payload.is_null() || payload_len == 0 {
        &[]
    } else {
        core::slice::from_raw_parts(payload as *const u8, payload_len as usize)
    };
    c.send_resp(seq, Vec::from(pl));
    0
}

pub type consumer_filter_t = Option<
    unsafe extern "C" fn(
        topic: *const c_char,
        topic_len: c_uint,
        msg_topic: *const c_char,
        msg_topic_len: c_uint,
        payload: *const c_char,
        payload_len: c_uint,
    ) -> c_int,
>;

#[no_mangle]
unsafe extern "C" fn create_consumer_with_filter(
    bus: *mut qbus,
    filter: consumer_filter_t,
) -> *mut qconsumer {
    if bus.is_null() {
        return null_mut();
    }
    let qb = Box::leak(Box::from_raw(bus as *mut qbus));
    if qb.priv_.is_null() {
        return null_mut();
    }
    let qbus = Box::leak(Box::from_raw(qb.priv_ as *mut QBus));
    let p = Box::into_raw(Box::new(
        qbus.create_consumer_with_filter(Box::new(CFilter::new(filter))),
    ));
    let c = Box::new(qconsumer {
        priv_: p as *mut c_void,
        free: Some(free_qconsumer),
        subscribe: Some(subscribe_consumer),
        unsubscribe: Some(unsubscribe_consumer),
        read: Some(read_qmessage),
        read_map: Some(read_map),
        send_resp: Some(send_qmessage_resp),
    });
    Box::into_raw(c)
}

struct CFilter {
    cb: consumer_filter_t,
}

impl CFilter {
    pub fn new(filter: consumer_filter_t) -> Self {
        Self { cb: filter }
    }
}

unsafe impl Send for CFilter {}
unsafe impl Sync for CFilter {}

impl QMessageFilter for CFilter {
    fn filter(&self, sub_topic: &str, msg: &QMessage) -> bool {
        if let Some(cb) = self.cb {
            let ret = unsafe {
                cb(
                    cstr!(sub_topic),
                    sub_topic.len() as c_uint,
                    cstr!(msg.topic),
                    msg.topic.len() as c_uint,
                    cstr!(msg.payload),
                    msg.payload.len() as c_uint,
                )
            };
            return if ret == 0 { false } else { true };
        }
        false
    }
}
